package references

import (
	"fmt"
	"jvm/instructions/base"
	"jvm/rtda"
	"jvm/rtda/heap"
	. "jvm/typedef"
)

// **************************************************
type INVOKE_STATIC struct {
	base.U2IndexInst
}

func (self *INVOKE_STATIC) Execute(frame *rtda.Frame) {
	methodRef := frame.Method().Class().ConstantPool().GetConstant(self.Index).(*heap.MethodRef)
	method := methodRef.ResolvedMethod()
	newClass := method.Class()
	if !newClass.IsInitialization() {
		frame.RevertNextPC()
		base.InitClass(frame.Thread(), newClass)
		return
	}
	if !method.IsStatic() {
		panic("java.lang.IncompatibleClassChangeError")
	}
	base.MethodInvoke(frame, method)

}

// **************************************************
type INVOKE_SPECIAL struct {
	base.U2IndexInst
}

func (self *INVOKE_SPECIAL) Execute(frame *rtda.Frame) {
	methodRef := frame.Method().Class().ConstantPool().GetConstant(self.Index).(*heap.MethodRef)
	method := methodRef.ResolvedMethod()
	if method.Name() == "<init>" && method.Class() != methodRef.Class() {
		panic("java.lang.NoSuchMethodError")
	}
	if method.IsStatic() {
		panic("java.lang.IncompatibleClassChangeError")
	}
	ref := frame.OPStack().GetRefFromTop(method.ArgSlotCount() - 1)
	if ref == nil {
		panic("java.lang.NullPointerException")
	}
	currentClass := frame.Method().Class()
	newClass := methodRef.Class()
	// if method.IsProtected() &&
	// 	currentClass.IsSubClassOf(method.Class()) &&
	// 	method.Class().PackageName() != currentClass.PackageName() &&
	// 	ref.Class() != currentClass &&
	// 	!currentClass.IsSubClassOf(ref.Class()) {
	// 		panic("java.lang.IllegalAccessError")
	// 	}
	//确保protected方法只能被声明该方法的类或子类调用
	invokeMethod := method
	if currentClass.IsSuper() &&
		currentClass.IsSubClassOf(newClass) &&
		method.Name() != "<init>" {
		invokeMethod = heap.FindMethodInClass(currentClass.SuperClass(), method.Name(), method.Descriptor())
	}
	if invokeMethod == nil || invokeMethod.IsAbstract() {
		panic("java.lang.AbstractMethodError")
	}
	base.MethodInvoke(frame, invokeMethod)

}

// **************************************************
type INVOKE_VIRTUAL struct {
	base.U2IndexInst
}

func (self *INVOKE_VIRTUAL) Execute(frame *rtda.Frame) {
	methodRef := frame.Method().Class().ConstantPool().GetConstant(self.Index).(*heap.MethodRef)
	method := methodRef.ResolvedMethod()
	if method.IsStatic() {
		panic("java.lang.IncompatibleClassChangeError")
	}
	ref := frame.OPStack().GetRefFromTop(method.ArgSlotCount() - 1)
	if ref == nil {
		if method.Name() == "println" {
			_println(frame.OPStack(), method.Descriptor())
			return
		}
		panic("java.lang.NullPointerException")
	}
	//newClass := methodRef.Class()
	// if method.IsProtected() &&
	// 	currentClass.IsSubClassOf(method.Class()) &&
	// 	method.Class().PackageName() != currentClass.PackageName() &&
	// 	ref.Class() != currentClass &&
	// 	!currentClass.IsSubClassOf(ref.Class()) {
	// 		panic("java.lang.IllegalAccessError")
	// 	}
	//确保protected方法只能被声明该方法的类或子类调用
	invokeMethod := heap.FindMethodInClass(ref.Class(), method.Name(), method.Descriptor())
	if invokeMethod == nil || invokeMethod.IsAbstract() {
		panic("java.lang.AbstractMethodError")
	}
	base.MethodInvoke(frame, invokeMethod)

}
func _println(stack *rtda.OPStack, descriptor string) {
	switch descriptor {
	case "(Z)V":
		fmt.Printf("%v\n", stack.PopInt() != 0)
	case "(C)V":
		fmt.Printf("%c\n", stack.PopInt())
	case "(B)V":
		fmt.Printf("%v\n", stack.PopInt())
	case "(S)V":
		fmt.Printf("%v\n", stack.PopInt())
	case "(I)V":
		fmt.Printf("%v\n", stack.PopInt())
	case "(F)V":
		fmt.Printf("%v\n", stack.PopFloat())
	case "(J)V":
		fmt.Printf("%v\n", stack.PopLong())
	case "(D)V":
		fmt.Printf("%v\n", stack.PopDouble())
	default:
		panic("println: " + descriptor)
	}
	stack.PopRef()
}

// **************************************************
type INVOKE_INTERFACE struct {
	index U2
}

func (self *INVOKE_INTERFACE) ReadOperands(reader *base.CodeReader) {
	self.index = reader.ReadU2()
	reader.ReadU1() // count
	reader.ReadU1() // must be 0
}
func (self *INVOKE_INTERFACE) Execute(frame *rtda.Frame) {
	methodRef := frame.Method().Class().ConstantPool().GetConstant(self.index).(*heap.InterfaceMethodRef)
	method := methodRef.ResolvedInterfaceMethod()
	if method.IsStatic() || method.IsPrivate() {
		panic("java.lang.IncompatibleClassChangeError")
	}
	ref := frame.OPStack().GetRefFromTop(method.ArgSlotCount() - 1)
	if ref == nil {
		panic("java.lang.NullPointerException")
	}
	if !ref.Class().IsImplements(method.Class()) {
		panic("java.lang.IncompatibleClassChangeError")
	}
	invokeMethod := heap.FindMethodInClass(ref.Class(), method.Name(), method.Descriptor())
	if invokeMethod == nil || invokeMethod.IsAbstract() {
		panic("java.lang.AbstractMethodError")
	}
	if !invokeMethod.IsPublic() {
		panic("java.lang.IllegalAccessError")
	}
	base.MethodInvoke(frame, invokeMethod)
}
